﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using UnityEngine;

public class BaseSocket
{
	/// <summary>
	/// Socket to connect server
	/// </summary>
	public Socket Socket;

	/// <summary>
	/// Whether connect the server
	/// </summary>
	public bool IsConnected
	{
		get { return Socket != null && Socket.Connected; }
	}

	/// <summary>
	/// Port
	/// </summary>
	private int port;

	/// <summary>
	/// IP address
	/// </summary>
	private IPAddress address;

	public event Action<byte[]> OnPack;
//	public event Action<byte[]> ReceiveCallback;
//	public event Action<byte[]> SendCallback;

	private int headLength;


	public BaseSocket(string address, int port)
	{
		headLength = ConstantDef.headLength;
		Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
		this.address = IPAddress.Parse(address);
		this.port = port;
	}

	/// <summary>
	/// Bind
	/// </summary>
	public void Bind()
	{
		Socket.Bind(new IPEndPoint(address, port));
	}

	/// <summary>
	/// Connect to server
	/// </summary>
	public void Connect()
	{
		try
		{
			Socket.BeginConnect(address, port, ConnectCallback, port);
		}
		catch (Exception ex)
		{
			MyLog.LogError("Connect Error : " + ex);
		}
	}

	private void ConnectCallback(IAsyncResult ar)
	{
		try
		{
			Socket.EndConnect(ar);
			MyLog.Log("Successfully Connect : " + port);

			//begin recevie
			BeginReceive();
		}
		catch (Exception ex)
		{
			MyLog.LogError("ConnectCB Error : " + ex);
		}
	}

	/// <summary>
	/// Send data
	/// </summary>
	public void Send(byte[] bytes)
	{
		try
		{
			if (IsConnected)
			{
				Socket.Send(bytes);
//				Socket.BeginSend(bytes, 0, bytes.Length, 0, SendCallback, data);
			}
		}
		catch (Exception ex)
		{
			MyLog.LogError("Send Data Error : " + ex);
			CloseSocket();
		}
	}

	/// <summary>
	/// Send data
	/// </summary>
	private void Send(SendData data)
	{
		try
		{
			if (IsConnected)
			{
				lock (data.AsynLock)
				{
					data.IsSending = true;

					byte[] bytes = new byte[data.Length + headLength];
					byte[] headBuffer = BitConverter.GetBytes(data.Length);

					Buffer.BlockCopy(headBuffer, 0, bytes, 0, headBuffer.Length);
					Buffer.BlockCopy(data.SendBuf, 0, bytes, headLength, data.Length);
					Socket.Send(bytes);
//					Socket.BeginSend(bytes, 0, bytes.Length, 0, SendCallback, data);
				}
			}
		}
		catch (Exception ex)
		{
			MyLog.LogError("Send Data Error : " + ex);
			CloseSocket();
		}
	}

	private void SendCallback(IAsyncResult ar)
	{
		SendData tcpData = ar.AsyncState as SendData;
		try
		{
			tcpData.IsSending = false;
			int result = Socket.EndSend(ar);
			//if End Sending
			if (result == tcpData.Length + headLength) {
				//just think about success case
//				MyLog.Log("Send result: " + result);
				if (tcpData.Ondone != null) {
					tcpData.Ondone(TcpState.Success);
				}
			}
			//else continue send
			else if (result != tcpData.Length + headLength)
			{
				//代议
//				Socket.BeginSend(tcpData.SendBuf, 0, tcpData.Length, 0, SendCB, tcpData);
			}
		}
		catch (Exception ex)
		{
			MyLog.LogError("Error when end sending : " + ex);
			if (!String.IsNullOrEmpty(ex.ToString()))
			{
				tcpData.ErrorMsg = ex.ToString();
				if (tcpData.Ondone != null) {
					tcpData.Ondone(TcpState.Error);
				}
			}
			CloseSocket();
		}
	}

	public byte[] RecvBuf = new byte[0];
	/// <summary>
	/// Receive data from server
	/// </summary>
	private void BeginReceive()
	{
		if (IsConnected)
		{
			try
			{
				byte[] temp = new byte[1024];

				Socket.BeginReceive(temp, 0, temp.Length, 0, ReceiveCallback, temp);
			}
			catch (Exception ex)
			{
				MyLog.LogError("Receive Error : " + ex);
				CloseSocket();
			}
		}
	}

	private void ReceiveCallback(IAsyncResult ar)
	{
		if (IsConnected)
		{
			try
			{
				int length = Socket.EndReceive(ar);
//				MyLog.Log("Receive " + length);
				if (length > 0)
				{
					byte[] bytes = ar.AsyncState as byte[];

					lock (RecvBuf) {
						Read(length, bytes);
					}
				}
				else
				{
					HeartCheck();
				}

				BeginReceive();
			}
			catch (Exception ex)
			{
				MyLog.LogError("ReceiveCallback : " + ex);
				CloseSocket();
			}
		}
	}


	void Read(int length, byte[] bytes)
	{
		byte[] tmp = new byte[RecvBuf.Length + length];

		if (RecvBuf.Length > 0)
		{
			Buffer.BlockCopy(RecvBuf,0,tmp, 0, RecvBuf.Length);
		}
		Buffer.BlockCopy(bytes, 0, tmp, RecvBuf.Length, length);
		RecvBuf = tmp;

		ParseData(RecvBuf);
	}

	private void ParseData(byte[] bytes)
	{
		int dataLength = GetLength(bytes);
		if (dataLength > 0)
		{
			int totalLength = dataLength + headLength;

			if (RecvBuf.Length >= totalLength)
			{
				byte[] data = new byte[dataLength];
				Buffer.BlockCopy(RecvBuf, headLength, data, 0, dataLength);
				if (OnPack != null)
				{
					OnPack(data);
				}

				int restLength = RecvBuf.Length - totalLength;
				byte[] restBuffer = new byte[restLength];
				Buffer.BlockCopy(RecvBuf, totalLength, restBuffer, 0, restLength);
				RecvBuf = restBuffer;

				ParseData(RecvBuf);
			}
		}
	}

	int GetLength(byte[] bytes)
	{
		if (bytes.Length < headLength)
			return 0;
		byte[] head = new byte[headLength];
		Buffer.BlockCopy(bytes, 0, head, 0, headLength);
		int length = BitConverter.ToInt16(head, 0);

		return length;
	}

	void HeartCheck()
	{
		Send(new SendData(MethodDef.Heart, "heart"));
	}

	//Close socket
	public void CloseSocket()
	{
		if (Socket != null)
		{
			ResetData();
			GC.Collect();
			Socket.Close();
		}
	}

	private void ResetSocket()
	{
		if (Socket != null) {
			ResetData();
			GC.Collect();
			Socket.Dispose();
		}
	}

	private void ResetData()
	{
		RecvBuf = new byte[0];
	}
}